#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include<unistd.h>

#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<fcntl.h>

#include "../heads/mklib_shell.h"
#include "../heads/traitements.h"


// Prends en argument un tableau de valeur, et redécoupe en fonction des espaces
// Prends la taille de elems en parramêtre et le remplace par la taille du tableau retourné
char ** decoupe(char **elems, const char sep, int *tailleTableau)
{
	char **res = NULL;
	int tailleRetour = 0;
	res = (char**)malloc(sizeof(char *)*tailleRetour);
	if(res == NULL) { perror("malloc"); return NULL; }
	//tailleRetour--; // pour pouvoir entrer correctement dans la boucle
	for(int i = 0; i < *tailleTableau; i++)
	{
		// On augmente la taille
		tailleRetour++;
		// On alloue une nouvelle case
		res = (char**)realloc(res, sizeof(char*)*tailleRetour);
		if(res == NULL) { perror("realloc"); return NULL; }

		res[tailleRetour-1] = elems[i];
		// Tant qu'il y a encore des éléments dans le tableau
		for(int j = 0; elems[i][j] != '\0'; j++)
			// On compare l'élément courant
			if(elems[i][j] == sep)
			{
				// remplacement de l'espace par une fin de chaine : 
				// la case de res qui contient la chaine avec l'espace va s'arrêter ici
				elems[i][j] = '\0'; 
				// Le tableau augmente de un
				tailleRetour++;
				// on alloue un peu plus de mémoire
				res = (char **)realloc(res, sizeof(char*)*tailleRetour);
				if(res == NULL) { perror("realloc"); return NULL; }
				// la case nouvelle pointe sur le carractère après l'espace
				res[tailleRetour-1] = &elems[i][j+1];
			}
	}
	*tailleTableau = tailleRetour;
	return res;
}

// Va remplacer le contenu de la chaine en argument par le contenu défini dans le Makefile
char *remplacement(char *cmd, mk_var_t **vars)
{
	if(cmd == NULL) return cmd; // si pas de commande
	if(cmd[0] != '$')  return cmd; // si pas une variable
	
	cmd++; // on retire le '$'
	for(int i = 0; vars[i] != NULL; i++)
	{
		if(strcmp(vars[i]->name, cmd) != 0) continue;
		// Sinon, c'est qu'il s'agit de la bonne variable

		int tailleCommande = 0;
		char *retour = NULL;
		retour = (char *)malloc(sizeof(char)*tailleCommande+1);


		// copie de toutes les valeurs de vars->value dans retour 
		for(int j = 0; vars[i]->value[j] != NULL; j++)
		{
			int tailleValue = strlen(vars[i]->value[j]),
			    tailleCommandeAvant = tailleCommande;
			

			// Tant que l'écart entre la taille de la commande avant, et actuellement, est plus petit que la vars->value
			while( (tailleCommande-tailleCommandeAvant) < tailleValue)
				retour = (char *)realloc(retour, sizeof(char)*(++tailleCommande));

			// copie de vars->value
			for(int k = 0; vars[i]->value[j][k] != '\0'; k++)
				retour[k+tailleCommandeAvant] = vars[i]->value[j][k];
			
			retour = (char *)realloc(retour, sizeof(char)*(++tailleCommande));
			retour[tailleCommande-1] = ' '; // Ajout d'un espace
		}
		retour[tailleCommande-1] = '\0'; // Remplacement du dernier espace par un carractere de fin de chaine
		return retour;
	}
	// Si on est sorti de la boucle
	cmd--; // On n'annule le ++
	return cmd;
}

// Execute la commande passée en argument, utilise le tube pour les connexions
int executeCommande(char **argv, makefile_t *makefile, int tube[2])
{
	
	// Va contenir la taille de argv
	int tailleTableau = 0;

	// Remplacement des variables de la commande par leurs valeur
	for(int i = 0; argv[i] != NULL; i++)
	{
		tailleTableau++;
		if(argv[i][0] == '$') 
		{
			// On remplace
			argv[i] = remplacement(argv[i], makefile->vars);
			if(argv[i] == NULL) { perror("Remplacement des variables"); return RAND_MAX; }
		}
	}

	// Après le remplacement, on peut avoir un argv[n] avec des espaces
	char **cmdARG = NULL;
	// la taille du tableau doit être initialisée : elle est utilisée dans la fonction decoupe
	int tailleTableauRedecoupe = tailleTableau;
	cmdARG = decoupe(argv, ' ', &tailleTableauRedecoupe);
	if(cmdARG == NULL) { perror("cmdARG NULL"); return RAND_MAX; }

	#ifdef _DEBUG // Cet affichage va afficher la commande après remplacement de la variable par sa valeur et son découpage
	printf("Execution de la commande : ");
	for(int i = 0; i < tailleTableauRedecoupe; i++)
		printf("%s ", cmdARG[i]);
	printf("\n");
	#endif

	// Il faut maintenant ajouter une case NULL à la fin de cmdARG pour l'utiliser dans le execvp
	cmdARG = (char**)realloc(cmdARG, (++tailleTableauRedecoupe)*sizeof(char*));
	cmdARG[tailleTableauRedecoupe-1] = NULL;

	printf("\n");

	// Traitement execution
		// On fork, puis on exec
	int statusWait; 
	
	switch((pid_child = fork()))
	{
		case -1 : 
			perror("Echec fork");
			return 1;
		case 0 : // Fils
			execvp(cmdARG[0], cmdARG);
			perror("Echec execvp");
			return 1;
		default : // pere, attends la fin du fils
			waitpid(pid_child, &statusWait, 0);
			pid_child = -1;
			break;
	}
	
	// nettoyage de la memoire
	for(int i = 0; i < tailleTableau; i++)
		if(argv[i] != NULL) free(argv[i]);

	if(cmdARG != NULL) free(cmdARG);

	// si le processus a terminé normalement, on retourne ce qu'il a retourné
	if(WIFEXITED(statusWait)) 
		return WEXITSTATUS(statusWait);

	// s'il ne s'est pas fini normalement (interruption, etc..)
	return RAND_MAX;
}

int traitementPCommande(pcommand_t *commande, makefile_t *makefile)
{
	int retour = 0;
	// si size(subcommands) > 1 => commande | commande | commande ...
	int pipeFD[2] = { -1, -1 };

	// Sauvegarde des trois descripteurs
	int savIN = dup(STDIN_FILENO), 
	    savOUT = dup(STDOUT_FILENO),
	    savERR = dup(STDERR_FILENO);

	for(int i = 0; commande->subcommands[i] != NULL; i++)
	{
		for(int j = 0; commande->subcommands[i]->redirs[j] != NULL; j++)
		{
			int fd = -1;
			redir_t *redir = commande->subcommands[i]->redirs[j];
			switch(redir->kind)
			{
				case RD_INFILE : // Cas commande < fichier
					#ifdef _DEBUG
					printf("Redir infile\n");
					#endif
					fd = open(redir->data.dt_tofile, O_RDONLY);
					if(fd == -1) 
					{ 
						perror("open"); 
						reapplicationDescripteurs(savIN, savOUT, savERR); 
						return 1; 
					}
					if(dup2(fd, STDIN_FILENO) == -1) 
					{ 
						perror("dup2"); 
						close(fd); 
						reapplicationDescripteurs(savIN, savOUT, savERR); 
						return 1; 
					}
					break;
				case RD_OUTFILE : // Cas commande > fichier 
					#ifdef _DEBUG
					printf("Redir outfile\n");
					printf("Fichier cible : %s\n", redir->data.dt_tofile);
					#endif
					fd = open(redir->data.dt_tofile, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
					if(fd == -1) 
					{ 
						perror("open"); 
						reapplicationDescripteurs(savIN, savOUT, savERR); 
						return 1; 
					}
					if(dup2(fd, STDOUT_FILENO) == -1) 
					{
						perror("dup2"); 
						close(fd); 
						reapplicationDescripteurs(savIN, savOUT, savERR); 
						return 1; 
					}
					break;
				case RD_APPFILE : // Cas commande >> fichier
					#ifdef _DEBUG
					printf("Redir appfile\n");
					printf("Fichier cible : %s\n", redir->data.dt_tofile);
					#endif
					fd = open(redir->data.dt_tofile, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
					if(fd == -1) 
					{ 
						perror("open"); 
						reapplicationDescripteurs(savIN, savOUT, savERR); 
						return 1; 
					}
					if(dup2(fd, STDOUT_FILENO) == -1) 
					{
						perror("dup2"); 
						close(fd); 
						reapplicationDescripteurs(savIN, savOUT, savERR); 
						return 1; 
					}
					break;
				case RD_DUP : // Cas commande &> 
					#ifdef _DEBUG
					printf("Redir dup de %d vers %d\n", redir->data.dt_dupfs.fdin, redir->data.dt_dupfs.fdout);
					#endif
					dup2(redir->data.dt_dupfs.fdout, redir->data.dt_dupfs.fdin);
					break;
			}
			if(fd != -1) close(fd);
		}
		
		if(commande->subcommands[i+1] != NULL) // S'il y a une suite
		{
			// Création du tube
			if(pipe(pipeFD) == -1) 
			{ 
				perror("pipe"); 
				reapplicationDescripteurs(savIN, savOUT, savERR); 
				return 1; 
			}
			// On connecte la sortie STDOUT normale sur l'entrée du tube et sa sortie sur stdin
			#ifdef _DEBUG
			printf("Creation du tube Lecture :%d <- Ecriture :%d\n", pipeFD[0], pipeFD[1]);
			 #endif
		}

		// On connecte la sortie du premier sur l'entrée du second
		#ifdef _DEBUG
		printf("\nExecution de ");
		for(int j = 0; commande->subcommands[i]->argv[j] != NULL; j++)
			printf("%s ", commande->subcommands[i]->argv[j]);
		printf("\n");
		#endif
		retour = executeCommande(commande->subcommands[i]->argv, makefile, pipeFD);
		
		// On stop ici si tout ne s'est pas bien passé
		if(retour != 0) 
		{ reapplicationDescripteurs(savIN, savOUT, savERR); return retour; }
	}
	if(pipeFD[0] != -1) close(pipeFD[0]);
	if(pipeFD[1] != -1) close(pipeFD[1]);
	reapplicationDescripteurs(savIN, savOUT, savERR);
	return retour;
}

int traitementCommande(command_t *commande, makefile_t *makefile)
{
	int retour;
	if(commande->simple)
		return traitementPCommande(commande->pcommand, makefile);
	else
		switch(commande->node.kind)
		{
			case CO_AND:
				if(traitementCommande(commande->node.left, makefile) == 0) // si elle est reussie
				return traitementCommande(commande->node.right, makefile);
			case CO_OR:
				if(traitementCommande(commande->node.left, makefile) == 0) // si elle est reussie
					return 0; // pas d'execution de la commande de droite
				return traitementCommande(commande->node.right, makefile);
			case CO_SEQ:
				retour = traitementCommande(commande->node.left, makefile);
				// si la commande ne s'est pas bien déroulée
				if(retour != 0) return retour;
				return traitementCommande(commande->node.right, makefile);
			
			// cas impossible, les trois cas étant mis ci dessus, mais retire un warning a la compilation
			default : return -1; 
		}
}

int traitementRegle(makefile_t *makefile, mk_rule_t *rule)
{
	// Si la regle recherchée précédement est un fichier par exemple
	if(rule == NULL) return 0;

	// Traitement des regles de chaque prérequis avant celle ci
	#ifdef _DEBUG
	printf("\nRegle : %s\n", rule->target);
	#endif

	// Recuperation des dates
	time_t dateAccesCible = 0, dateAccesRequis = 1;
		// Il faut verifier que l'un comme l'autre sont des fichiers
	if(access(rule->target, F_OK) == 0)
	{
		struct stat statCible;
		if(stat(rule->target, &statCible) == -1)
		{ perror("stat"); return -1; }
		dateAccesCible = statCible.st_mtime;
	}
	// Ci la cible n'est pas un fichier, cela veut dire qu'il faut executer toutes les dependances
	// le dateAccesCible est laissé à 0
	#ifdef _DEBUG
	printf("\tDate de la regle : %ld\n", dateAccesCible);
	#endif
	
	for(int i = 0; rule->prereqs[i] != NULL; i++)
	{
		#ifdef _DEBUG
		printf("\t%s requiers : %s\n", rule->target, rule->prereqs[i]);
		#endif
		
		//Traitement de la regle uniquement si la cible est plus ancienne que le prerequis
		if(access(rule->prereqs[i], F_OK) == 0)
		{
			struct stat statRequis;
			if(stat(rule->prereqs[i], &statRequis) == -1)
			{ perror("stat"); return -1; }
			dateAccesRequis = statRequis.st_mtime;
		}
		// De même , si le prerequis n'est pas un fichier, la date est laissé à 1, car 1 > 0 et la règle sera bien exé&cuté
		
		#ifdef _DEBUG
		printf("\tDate du prerequis %s : %ld\n\n", rule->prereqs[i], dateAccesRequis);
		#endif

		if(dateAccesRequis > dateAccesCible)
		{
			#ifdef _DEBUG
			printf("\t%s execute : %s\n", rule->target, rule->prereqs[i]);
			#endif
			if(traitementRegle(makefile, trouveRegle(makefile, rule->prereqs[i])) != 0)
			{
				usage("minimake", 4, rule->prereqs[i]);
				return -1;
			}
		}
	}
	
	// Traitement de cette regle
	for(int j = 0; rule->recipe[j] != NULL; j++)
		if(traitementCommande(rule->recipe[j], makefile) != 0)
		{ usage("minimake", 4, rule->target); return 1; }

	return 0;
}

